export default function (self) {
  // Utility functions to help testing keepalive requests.

  // Returns a URL to an iframe that loads a keepalive URL on iframe loaded.
  //
  // The keepalive URL points to a target that stores `token`. The token will then
  // be posted back on iframe loaded to the parent document.
  // `method` defaults to GET.
  // `frameOrigin` to specify the origin of the iframe to load. If not set,
  // default to a different site origin.
  // `requestOrigin` to specify the origin of the fetch request target.
  // `sendOn` to specify the name of the event when the keepalive request should
  // be sent instead of the default 'load'.
  // `mode` to specify the fetch request's CORS mode.
  // `disallowCrossOrigin` to ask the iframe to set up a server that disallows
  // cross origin requests.
  function getKeepAliveIframeUrl(
    token,
    method,
    {
      frameOrigin = "DEFAULT",
      requestOrigin = "",
      sendOn = "load",
      mode = "cors",
      disallowCrossOrigin = false,
    } = {}
  ) {
    const https = location.protocol.startsWith("https");
    frameOrigin =
      frameOrigin === "DEFAULT"
        ? get_host_info()[
            https ? "HTTPS_NOTSAMESITE_ORIGIN" : "HTTP_NOTSAMESITE_ORIGIN"
          ]
        : frameOrigin;
    return (
      `${frameOrigin}/fetch/api/resources/keepalive-iframe.html?` +
      `token=${token}&` +
      `method=${method}&` +
      `sendOn=${sendOn}&` +
      `mode=${mode}&` +
      (disallowCrossOrigin ? `disallowCrossOrigin=1&` : ``) +
      `origin=${requestOrigin}`
    );
  }
  self.getKeepAliveIframeUrl = getKeepAliveIframeUrl;

  // Returns a different-site URL to an iframe that loads a keepalive URL.
  //
  // By default, the keepalive URL points to a target that redirects to another
  // same-origin destination storing `token`. The token will then be posted back
  // to parent document.
  //
  // The URL redirects can be customized from `origin1` to `origin2` if provided.
  // Sets `withPreflight` to true to get URL enabling preflight.
  function getKeepAliveAndRedirectIframeUrl(
    token,
    origin1,
    origin2,
    withPreflight
  ) {
    const https = location.protocol.startsWith("https");
    const frameOrigin =
      get_host_info()[
        https ? "HTTPS_NOTSAMESITE_ORIGIN" : "HTTP_NOTSAMESITE_ORIGIN"
      ];
    return (
      `${frameOrigin}/fetch/api/resources/keepalive-redirect-iframe.html?` +
      `token=${token}&` +
      `origin1=${origin1}&` +
      `origin2=${origin2}&` +
      (withPreflight ? `with-headers` : ``)
    );
  }

  async function iframeLoaded(iframe) {
    return new Promise((resolve) => iframe.addEventListener("load", resolve));
  }

  // Obtains the token from the message posted by iframe after loading
  // `getKeepAliveAndRedirectIframeUrl()`.
  async function getTokenFromMessage() {
    return new Promise((resolve) => {
      window.addEventListener(
        "message",
        (event) => {
          resolve(event.data);
        },
        { once: true }
      );
    });
  }

  // Tells if `token` has been stored in the server.
  async function queryToken(token) {
    const response = await fetch(`../resources/stash-take.py?key=${token}`);
    const json = await response.json();
    return json;
  }

  // A helper to assert the existence of `token` that should have been stored in
  // the server by fetching ../resources/stash-put.py.
  //
  // This function simply wait for a custom amount of time before trying to
  // retrieve `token` from the server.
  // `expectTokenExist` tells if `token` should be present or not.
  //
  // NOTE:
  // In order to parallelize the work, we are going to have an async_test
  // for the rest of the work. Note that we want the serialized behavior
  // for the steps so far, so we don't want to make the entire test case
  // an async_test.
  function assertStashedTokenAsync(
    testName,
    token,
    { expectTokenExist = true } = {}
  ) {
    async_test((test) => {
      new Promise((resolve) => test.step_timeout(resolve, 3000 /*ms*/))
        .then(
          test.step_func(() => {
            return queryToken(token);
          })
        )
        .then(
          test.step_func((result) => {
            if (expectTokenExist) {
              assert_equals(result, "on", `token should be on (stashed).`);
              test.done();
            } else {
              assert_not_equals(
                result,
                "on",
                `token should not be on (stashed).`
              );
              return Promise.reject(`Failed to retrieve token from server`);
            }
          })
        )
        .catch(
          test.step_func((e) => {
            if (expectTokenExist) {
              test.unreached_func(e);
            } else {
              test.done();
            }
          })
        );
    }, testName);
  }

  /**
   * In an iframe, and in `load` event handler, test to fetch a keepalive URL that
   * involves in redirect to another URL.
   *
   * `unloadIframe` to unload the iframe before verifying stashed token to
   * simulate the situation that unloads after fetching. Note that this test is
   * different from `keepaliveRedirectInUnloadTest()` in that the latter
   * performs fetch() call directly in `unload` event handler, while this test
   * does it in `load`.
   */
  function keepaliveRedirectTest(
    desc,
    {
      origin1 = "",
      origin2 = "",
      withPreflight = false,
      unloadIframe = false,
      expectFetchSucceed = true,
    } = {}
  ) {
    desc =
      `[keepalive][iframe][load] ${desc}` +
      (unloadIframe ? " [unload at end]" : "");
    promise_test(async (test) => {
      const tokenToStash = token();
      const iframe = document.createElement("iframe");
      iframe.src = getKeepAliveAndRedirectIframeUrl(
        tokenToStash,
        origin1,
        origin2,
        withPreflight
      );
      document.body.appendChild(iframe);
      await iframeLoaded(iframe);
      assert_equals(await getTokenFromMessage(), tokenToStash);
      if (unloadIframe) {
        iframe.remove();
      }

      assertStashedTokenAsync(desc, tokenToStash, {
        expectTokenExist: expectFetchSucceed,
      });
    }, `${desc}; setting up`);
  }
  self.keepaliveRedirectTest = keepaliveRedirectTest;

  /**
   * Opens a different site window, and in `unload` event handler, test to fetch
   * a keepalive URL that involves in redirect to another URL.
   */
  function keepaliveRedirectInUnloadTest(
    desc,
    {
      origin1 = "",
      origin2 = "",
      url2 = "",
      withPreflight = false,
      expectFetchSucceed = true,
    } = {}
  ) {
    desc = `[keepalive][new window][unload] ${desc}`;

    promise_test(async (test) => {
      const targetUrl =
        `${HTTP_NOTSAMESITE_ORIGIN}/fetch/api/resources/keepalive-redirect-window.html?` +
        `origin1=${origin1}&` +
        `origin2=${origin2}&` +
        `url2=${url2}&` +
        (withPreflight ? `with-headers` : ``);
      const w = window.open(targetUrl);
      const token = await getTokenFromMessage();
      w.close();

      assertStashedTokenAsync(desc, token, {
        expectTokenExist: expectFetchSucceed,
      });
    }, `${desc}; setting up`);
  }
  self.keepaliveRedirectInUnloadTest = keepaliveRedirectInUnloadTest;

  /**
   * utility to create pending keepalive fetch requests
   * The pending request state is achieved by ensuring the server (trickle.py) does not
   * immediately respond to the fetch requests.
   * The response delay is set as a url parameter.
   */

  function createPendingKeepAliveRequest(delay, remote = false) {
    // trickle.py is a script that can make a delayed response to the client request
    const trickleRemoteURL =
      get_host_info().HTTPS_REMOTE_ORIGIN +
      "/fetch/api/resources/trickle.py?count=1&ms=";
    const trickleLocalURL =
      get_host_info().HTTP_ORIGIN +
      "/fetch/api/resources/trickle.py?count=1&ms=";
    url = remote ? trickleRemoteURL : trickleLocalURL;

    const body = "*".repeat(10);
    return fetch(url + delay, { keepalive: true, body, method: "POST" })
      .then((res) => {
        return res.text();
      })
      .then(() => {
        return new Promise((resolve) => step_timeout(resolve, 1));
      })
      .catch((error) => {
        return Promise.reject(error);
      });
  }
  self.createPendingKeepAliveRequest = createPendingKeepAliveRequest;
}
